package com.small.nacos.config.server.service.responsitory.extrnal;

import small.common.core.constants.CommonConstants;
import small.common.crypto.util.MD5Util;
import small.common.core.util.StringUtil;
import com.small.nacos.api.config.model.ConfigInfo;
import com.small.nacos.config.server.configuration.ConditionOnExternalStorage;
import com.small.nacos.config.server.service.datasource.DynamicDataSource;
import com.small.nacos.config.server.service.responsitory.PersistService;
import com.small.nacos.core.datasource.DataSourceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.PostConstruct;
import java.sql.*;
import java.util.Map;

import static com.small.nacos.config.server.service.responsitory.RowMapperManager.CONFIG_INFO_ROW_MAPPER;

@Conditional(ConditionOnExternalStorage.class)
@Component
public class ExternalStoragePersistServiceImpl implements PersistService {

    protected static final Logger LOGGER = LoggerFactory.getLogger(ExternalStoragePersistServiceImpl.class);

    private DataSourceService dataSourceService;

    protected TransactionTemplate tjt;

    protected JdbcTemplate jt;

    @PostConstruct
    public void init() {
        dataSourceService = DynamicDataSource.getInstance().getDataSource();

        jt = getJdbcTemplate();
        tjt = getTransactionTemplate();
    }

    @Override
    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map<String, Object> configAdvanceInfo) {

        try {
            //添加配置信息
            addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo);
        // 主键唯一冲突
        } catch (DataIntegrityViolationException e) {
            //更新配置信息
            updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo);
        }

    }

    @Override
    public void addConfigInfo(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map<String, Object> configAdvanceInfo) {
        boolean result = tjt.execute(status -> {
            try {
                long configId = addConfigInfoAtomic(-1, srcIp, srcUser, configInfo, time, configAdvanceInfo);
            } catch (CannotGetJdbcConnectionException e) {
                LOGGER.error("[db-error] " + e.toString(), e);
                throw e;
            }
            return Boolean.TRUE;
        });
    }

    @Override
    public long addConfigInfoAtomic(final long configId, final String srcIp, final String srcUser,
                                    final ConfigInfo configInfo, final Timestamp time, Map<String, Object> configAdvanceInfo) {
        final String appNameTmp =
                StringUtil.isBlank(configInfo.getAppName()) ? StringUtil.EMPTY : configInfo.getAppName();
        final String tenantTmp =
                StringUtil.isBlank(configInfo.getTenant()) ? StringUtil.EMPTY : configInfo.getTenant();

        final String desc = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("desc");
        final String use = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("use");
        final String effect = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("effect");
        final String type = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("type");
        final String schema = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("schema");


        final String md5Tmp = MD5Util.md5Hex(configInfo.getContent(), CommonConstants.ENCODE);

        KeyHolder keyHolder = new GeneratedKeyHolder();

        final String sql =
                "INSERT INTO config_info(data_id,group_id,tenant_id,app_name,content,md5,src_ip,src_user,gmt_create,"
                        + "gmt_modified,c_desc,c_use,effect,type,c_schema) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

        try {
            jt.update(new PreparedStatementCreator() {
                @Override
                public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                    PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                    ps.setString(1, configInfo.getDataId());
                    ps.setString(2, configInfo.getGroup());
                    ps.setString(3, tenantTmp);
                    ps.setString(4, appNameTmp);
                    ps.setString(5, configInfo.getContent());
                    ps.setString(6, md5Tmp);
                    ps.setString(7, srcIp);
                    ps.setString(8, srcUser);
                    ps.setTimestamp(9, time);
                    ps.setTimestamp(10, time);
                    ps.setString(11, desc);
                    ps.setString(12, use);
                    ps.setString(13, effect);
                    ps.setString(14, type);
                    ps.setString(15, schema);
                    return ps;
                }
            }, keyHolder);
            Number nu = keyHolder.getKey();
            if (nu == null) {
                throw new IllegalArgumentException("insert config_info fail");
            }
            return nu.longValue();
        } catch (CannotGetJdbcConnectionException e) {
            LOGGER.error("[db-error] " + e.toString(), e);
            throw e;
        }
    }

    @Override
    public void updateConfigInfo(ConfigInfo configInfo, String srcIp, String srcUser, Timestamp time, Map<String, Object> configAdvanceInfo) {
        boolean result = tjt.execute(status -> {
            try {
                ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(),
                        configInfo.getTenant());
                String appNameTmp = oldConfigInfo.getAppName();
                if (configInfo.getAppName() == null) {
                    configInfo.setAppName(appNameTmp);
                }
                updateConfigInfoAtomic(configInfo, srcIp, srcUser, time, configAdvanceInfo);

            } catch (CannotGetJdbcConnectionException e) {
                LOGGER.error("[db-error] " + e.toString(), e);
                throw e;
            }
            return Boolean.TRUE;
        });
    }

    @Override
    public ConfigInfo findConfigInfo(final String dataId, final String group, final String tenant) {
        final String tenantTmp = StringUtil.isBlank(tenant) ? StringUtil.EMPTY : tenant;
        try {
            return this.jt.queryForObject(
                    "SELECT ID,data_id,group_id,tenant_id,app_name,content,md5,type FROM config_info WHERE data_id=? AND group_id=? AND tenant_id=?",
                    new Object[] {dataId, group, tenantTmp}, CONFIG_INFO_ROW_MAPPER);
        } catch (EmptyResultDataAccessException e) { // Indicates that the data does not exist, returns null.
            return null;
        } catch (CannotGetJdbcConnectionException e) {
            LOGGER.error("[db-error] " + e.toString(), e);
            throw e;
        }
    }


    @Override
    public void updateConfigInfoAtomic(final ConfigInfo configInfo, final String srcIp, final String srcUser,
                                       final Timestamp time, Map<String, Object> configAdvanceInfo) {
        String appNameTmp = StringUtil.isBlank(configInfo.getAppName()) ? StringUtil.EMPTY : configInfo.getAppName();
        String tenantTmp = StringUtil.isBlank(configInfo.getTenant()) ? StringUtil.EMPTY : configInfo.getTenant();
        final String md5Tmp = MD5Util.md5Hex(configInfo.getContent(), CommonConstants.ENCODE);
        String desc = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("desc");
        String use = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("use");
        String effect = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("effect");
        String type = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("type");
        String schema = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("schema");

        try {
            jt.update("UPDATE config_info SET content=?, md5 = ?, src_ip=?,src_user=?,gmt_modified=?,"
                            + "app_name=?,c_desc=?,c_use=?,effect=?,type=?,c_schema=? "
                            + "WHERE data_id=? AND group_id=? AND tenant_id=?", configInfo.getContent(), md5Tmp, srcIp, srcUser,
                    time, appNameTmp, desc, use, effect, type, schema, configInfo.getDataId(), configInfo.getGroup(),
                    tenantTmp);
        } catch (CannotGetJdbcConnectionException e) {
            LOGGER.error("[db-error] " + e.toString(), e);
            throw e;
        }
    }

    public JdbcTemplate getJdbcTemplate() {
        return this.dataSourceService.getJdbcTemplate();
    }

    public TransactionTemplate getTransactionTemplate() {
        return this.dataSourceService.getTransactionTemplate();
    }
}
